<?php


namespace seecms\lib\provider;

use Jenssegers\Agent\Agent;
use magein\utils\Client;
use Psr\Cache\InvalidArgumentException;
use seecms\db\SeeDbException;
use seecms\See;
use seecms\lib\Auth;
use seecms\SeeLang;
use magein\utils\DataSecurity;
use magein\utils\Result;

class UserLogin
{
    // 账号密码登录
    const TYPE_ACCOUNT = 1;

    // 手机号码+验证码
    const TYPE_PHONE_CODE = 2;

    // app扫码登录
    const TYPE_SCAN_APP = 3;

    /**
     * @return string[]
     */
    public function support(): array
    {
        return [
            self::TYPE_PHONE_CODE => __('login.method_phone_code'),
            self::TYPE_SCAN_APP => __('login.method_app_scan'),
        ];
    }

    public function doLogin($data): Result
    {
        extract($data);

        if ($type == self::TYPE_PHONE_CODE) {
            // 手机号码+验证码登录
            $result = $this->phone($phone, $code);
        } elseif ($type == self::TYPE_SCAN_APP) {
            // app扫码登录
            $result = $this->scan($user_id, $token);
        } else {
            // 账号密码登录
            $result = $this->username($username, $password);
        }

        return $result;
    }

    /**
     * 保持登录
     * @param $data
     * @return bool
     */
    public function keepLogin($data): bool
    {
        if (empty($data)) {
            return true;
        }

        extract($data);

        $keep = 'keep';
        if ($keep_login ?? '') {
            // 获取已经设置的数据
            $data = $this->keepLoginData() ?: [];
            // 重置保持登录的数据
            if (isset($username)) {
                $data['username'] = $username;
            }
            if (isset($password)) {
                $data['password'] = $password;
            }
            if (isset($phone)) {
                $data['phone'] = $phone;
            }
            $security = new DataSecurity();
            setcookie($keep, $security->encrypt(json_encode($data)), 0, '/');
        } else {
            setcookie($keep, '');
        }
        return true;
    }

    /**
     * 保持登录数据
     * @return array
     */
    public function keepLoginData(): array
    {
        $keep = 'keep';
        $data = ($_COOKIE[$keep] ?? null) ?: null;
        if (empty($data)) {
            return [];
        }
        return json_decode((new DataSecurity())->decrypt($data), true);
    }


    /**
     * @param $user
     * @param int|null $status
     * @param string $reason
     * @return bool
     */
    public function log($user, int $status = null, string $reason = ''): bool
    {
        try {
            // 记录登录日志
            $agent = new Agent();
            See::table()->user_login->insert([
                'user_id' => $user['id'],
                'ip' => request()->ip(),
                'browser' => $agent->browser(),
                'browser_version' => $agent->version($agent->browser()),
                'device' => $agent->device(),
                'platform' => $agent->platform(),
                'platform_version' => $agent->version($agent->platform()),
                'is_mobile' => $agent->isMobile() ? 1 : 0,
                'status' => $status ? 1 : 0,
                'reason' => $reason,
                'user_agent' => $agent->getUserAgent(),
            ]);
        } catch (SeeDbException $dbException) {

        }

        return true;
    }

    /**
     * 扫码登录
     * @return string
     */
    public function appScan()
    {
        $token = sha1(uniqid());
        // 十分钟有效
        $expire = time() + 600;

        cache($token, $expire);

        return $token;
    }

    /**
     * @param $username
     * @param $password
     * @return Result
     */
    public function username($username, $password): Result
    {
        if (empty($username) || empty($password)) {
            return Result::error(SeeLang::login('username_password_null'));
        }

        $result = $this->checkSecurity();
        if ($result->getCode()) {
            return $result;
        }

        $user = See::table()->user->where('username', $username)->find();
        if (empty($user)) {
            return Result::error(SeeLang::user('not_found'));
        }

        if (!Auth::checkPassword($password, $user['password'])) {
            return $this->fail($user, SeeLang::login('password_error'));
        }

        if ($user['status'] == 0) {
            return $this->fail($user, SeeLang::login('username_forbid'));
        }

        return $this->success($user, true);
    }

    /**
     * @param $phone
     * @param $code
     * @return Result
     */
    public function phone($phone, $code): Result
    {
        if (empty($phone)) {
            return Result::error(SeeLang::login('enter_phone'));
        }

        if (empty($code)) {
            return Result::error(SeeLang::login('enter_code'));
        }

        if (!See::config()->debug()) {
            return Result::error(SeeLang::login('enter_code_error'));
        }

        $result = $this->checkSecurity();
        if ($result->getCode()) {
            return $result;
        }

        $user = See::table()->user->where('phone', $phone)->find();
        if (empty($user)) {
            return Result::error(SeeLang::user('not_found'));
        }

        return $this->success($user);
    }

    public function wx($data): Result
    {
        if ($data) {
            $expire_time = $data['expire_time'] ?? '';
            if ($expire_time < time()) {
                return Result::error(SeeLang::login('qrcode_expire'));
            }
            $user_id = $data['user_id'] ?? '';
            if (empty($user_id)) {
                return Result::error(SeeLang::login('qrcode_expire'));
            }

            $user = See::table()->user->find($user_id);
            if (empty($user)) {
                return Result::error(SeeLang::user('not_found'));
            }

            return $this->success($user);
        }

        return Result::error(SeeLang::login('fail'));
    }

    public function scan($user_id, $token): Result
    {
        if (empty($token) || empty($user_id)) {
            return Result::error(SeeLang::login('qrcode_refresh'));
        }
        $value = cache($token);
        if (empty($value) || $value < time()) {
            return Result::error(SeeLang::login('qrcode_expire'));
        }

        $user = See::table()->user->find($user_id);

        if (empty($user)) {
            return Result::error(SeeLang::user('not_found'));
        }

        return $this->success($user);
    }

    /**
     * 验证登录方式
     * @return Result
     */
    public function checkSecurity(): Result
    {
        $setting = See::provider()->general->login;

        extract($setting);
        if ($mac_list ?? '') {
            $mac_list = explode(',', $mac_list);
            if (!in_array($mac_address, $mac_list)) {
                return Result::error(SeeLang::login('mac_forbid'));
            }
        }

        if ($white_list ?? '') {
            $white_list = explode(',', $white_list);
            if (!in_array(request()->ip(), $white_list)) {
                return Result::error(SeeLang::login('ip_forbid'));
            }
        }

        return Result::success();
    }


    public function logout()
    {
        try {

            See::cache()->delete('user_login_heart_' . Auth::user()->uuid());

            $token = $_COOKIE['token'] ?? '';
            See::cache()->delete($token);
            setcookie('token', '', time() - 1);
        } catch (\Psr\SimpleCache\InvalidArgumentException $e) {

        }
    }

    /**
     * 设置登录标记
     * @param $user
     * @return string
     * @throws InvalidArgumentException
     */
    public function makeToken($user): string
    {
        $token = md5(uniqid());
        // 设置登录标识
        setcookie('token', $token, 0, '/');
        // 记录登录信息
        $ttl = intval(See::provider()->general->login['effective_hour'] ?? 0);
        $ttl = $ttl >= 300 ? $ttl : 1800;
        See::cache()->set($token, $user, $ttl);
        sleep(1);

        // 设置用户心跳
        Auth::user()->setHeart();

        return $token;
    }


    /**
     * @param $user
     * @param bool $check_together
     * @return Result
     */
    protected function success($user, bool $check_together = false): Result
    {

        // 验证是否已经在线
        if ($check_together) {
            $together = See::provider()->general->login['together'] ?? 1;
            if (!$together) {
                $heart = See::cache()->get('user_login_heart_' . $user['id']);
                $token = $heart['token'] ?? '';
                $expire_time = $heart['expire_time'] ?? '';
                if ($token && $expire_time > time()) {
                    return Result::error(SeeLang::login('together_forbid'));
                }
                // 删除用户登录的缓存信息
                See::cache()->delete($token);
            }
        }

        try {

            $this->log($user, 1);

            $token = $this->makeToken($user);

        } catch (InvalidArgumentException|SeeDbException $e) {
            return Result::error(SeeLang::login('fail'));
        }

        return Result::success($token, SeeLang::login('success'));
    }

    /**
     * @param $user
     * @param $reason
     * @return Result
     */
    protected function fail($user, $reason = null): Result
    {
        $this->log($user, 0, $reason);

        return Result::error($reason);
    }
}